package com.dmw.demo.utils;

import com.dmw.demo.entity.DocInfo;
import com.dmw.demo.entity.Pos;
import com.dmw.demo.entity.Doc;
import com.dmw.demo.entity.Weight;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/**
 * 通过这个类，来完成整个搜索过程
 */
public class DocSearcher {

    // 索引实例
    private Index index = new Index();

    // 停用词实例
    private static HashSet<String> stopWordTable = new HashSet<>();

    public DocSearcher(){
        index.load();
        load();
    }

    /**
     * 搜索
     *
     * @param query 搜索词
     * @return 搜索结果的集合
     */
    public List<Doc> search(String query) {
        // 1. 分词
        List<Term> oldTerms = ToAnalysis.parse(query).getTerms();
        List<Term> terms = new ArrayList<>();
        // 针对分词进行过滤
        for(Term term: oldTerms){
            if(!stopWordTable.contains(term.getName())){
                terms.add(term);
            }
        }
        // 2. 针对分析结果查倒排
        // 保存所有分词查询的 weight
        List<List<Weight>> termResult = new ArrayList<>();
        for (Term term : terms) {
            String word = term.getName();
            List<Weight> invertedList = index.getInverted(word);
            // 判断该查询词是否存在
            if (invertedList == null) {
                continue;
            }
            // 追加一组元素
            termResult.add(invertedList);
        }

        // 3. 针对多个分词结果得到的 weight 进行合并
        List<Weight> allTermResult = mergeResult(termResult);

        // 4. 针对触发结果排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                return o2.getWeight() - o1.getWeight();
            }
        });
        // 5. 包装结果，根据正排结果进行包装
        List<Doc> docs = new ArrayList<>();
        for (Weight weight : allTermResult) {
            DocInfo docInfo = index.getDocInfo(weight.getDocId());
            Doc doc = new Doc();
            doc.setTitle(docInfo.getTitle());
            doc.setUrl(docInfo.getUrl());
            doc.setDesc(getDesc(docInfo.getContent(), terms));
            docs.add(doc);
        }
        return docs;
    }

    // 将多个单词搜到的 weight 链表，进行 docId 相同合并，权重值相加
    private List<Weight> mergeResult(List<List<Weight>> source) {

        // 1. 针对每一行进行排序
        for(List<Weight> curRow: source){
            curRow.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId() - o2.getDocId();
                }
            });
        }
        // 2. 借助优先级队列，针对行进行合并
        List<Weight> target = new ArrayList<>();
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                Weight weight1 = source.get(o1.getRow()).get(o1.getCol());
                Weight weight2 = source.get(o2.getRow()).get(o2.getCol());

                return weight1.getDocId() - weight2.getDocId();
            }
        });
        // 初始化队列，把每一行的第一个元素放到队列中。
        for(int row=0; row<source.size(); row++){
            queue.offer(new Pos(row, 0));
        }
        // 循环的取队列首元素
        while (!queue.isEmpty()){
            Pos pos = queue.poll();
            Weight curWeight = source.get(pos.getRow()).get(pos.getCol());
            if(target.size() > 0){
                Weight lastWeight = target.get(target.size() - 1);
                if(lastWeight.getDocId() == curWeight.getDocId()){
                    lastWeight.setWeight(lastWeight.getWeight() + curWeight.getWeight());
                }else {
                    target.add(curWeight);
                }
            }else{
                target.add(curWeight);
            }
            // 把当前元素处理完后，将对于元素的光标后移
            Pos newPos = new Pos(pos.getRow(), pos.getCol() + 1);
            if(newPos.getCol() >= source.get(newPos.getRow()).size()){
                continue;
            }
            queue.offer(newPos);
        }
        return target;
    }

    private String getDesc(String content, List<Term> terms) {
        // 遍历分词结果，查看哪个结果在 content 中存在
        // 分词库会将词分为全小写，必须把正文全部转小写再查询
        content = content.toLowerCase();
        int pos = -1;
        for(Term term: terms){
            String word = term.getName();
            // 全字匹配，需要保证该词在正文中是独立的词，而不是一部分，但如果出现单词旁边是英文符号，则能够搜索到内容，并且内容也存在搜索词，
            // 但得不到 pos，故返回结果可能得到的 desc 是不正确的
            // (优化，通过正则）
            // indexOf 不能进行正则匹配
            // pos = content.indexOf(" " + word + " ");
            content = content.replaceAll(RegexPatterns.WORD_SYMBOL_SEPARATION_REGEX + word + RegexPatterns.WORD_SYMBOL_SEPARATION_REGEX, " " + word + " ");
            pos = content.indexOf(" " + word + " ");
            if(pos >= 0){
                break;
            }
        }
        // 查询词只在标题中
        if(pos == -1){
            if(content.length() > 160){
                return content.substring(0, 160) + "...";
            }
            return content;
        }
        // 拼接描述（个人定义往前 60字符，往后 160字符）
        String desc = "";
        int begin = pos < 60? 0: pos-60;
        if(begin + 160 > content.length()){
            desc = content.substring(begin);
        }else{
            desc = content.substring(begin, begin+160) + "...";
        }
        // 搜索词逻辑标红，在描述结果中，将包含的搜索词外层加上 <i> 标签
        for(Term term: terms){
            String world = term.getName();
            desc = desc.replaceAll(" " + world + " ", " <i class='search-i'>" + world +"</i> ");
        }
        return desc;
    }

    public void load(){
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(SystemConstants.FILE_STOP_WORD_PATH + SystemConstants.FILE_STOP_WORD_NAME))){
            StringBuilder stringBuilder = new StringBuilder();
            while(true) {
                // 读取一行
                String line = bufferedReader.readLine();
                if(line == null){
                    // 读取文件完毕
                    break;
                }
                stopWordTable.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//    public static void main(String[] args) {
//        DocSearcher searcher = new DocSearcher();
//        List<QueryResult> arrayList = searcher.search("ArrayList");
//        for(QueryResult queryResult: arrayList){
//            System.out.println(queryResult);
//        }
//    }
}
